//
//  AssetGridViewController.swift
//  GifUsage
//
//  Created by Ting Han on 2020/12/19.
//  Copyright © 2020 Ting Han. All rights reserved.
//

import UIKit
import Photos
import PhotosUI

class AssetGridViewController: UIViewController {
    var fetchResult: PHFetchResult<PHAsset>!
    private let imageManager = PHCachingImageManager()
    private var previousPreheatRect = CGRect.zero
    private var thumbnailSize: CGSize!
    
    var column: CGFloat = 3
    let padding: CGFloat = 1
    
    override func viewDidLoad() {
        super.viewDidLoad()
        commonInit()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateCachedAssets()
    }
    
    deinit {
        PHPhotoLibrary.shared().unregisterChangeObserver(self)
        print("deinit AssetGridViewController ")
    }
    
    lazy var flowLayout: UICollectionViewFlowLayout = {
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.minimumLineSpacing = padding
        flowLayout.minimumInteritemSpacing = padding
        flowLayout.scrollDirection = .vertical
        return flowLayout
    }()
    
    lazy var collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: flowLayout)
        collectionView.backgroundColor = .white
        collectionView.delegate = self
        collectionView.dataSource = self
        return collectionView
    }()
}

// MARK: Private
extension AssetGridViewController {
    private func commonInit() {
        setupNavigationBar()
        loadAllAsset()
        resetCachedAsset()
        setupSubviews()
    }
    
    private func setupNavigationBar() {
        navigationItem.title = "相簿"
        view.backgroundColor = .white
//        navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
//        navigationController?.navigationBar.shadowImage = UIImage()
//        navigationController?.navigationBar.backgroundColor = .clear
    }
    
    private func setupSubviews() {
        view.addSubview(collectionView)
        collectionView.register(AssetGridCell.self, forCellWithReuseIdentifier: String(describing: AssetGridCell.self))
        collectionView.frame = view.bounds
        let itemWidth = self.itemWidth
        flowLayout.itemSize = CGSize(width: itemWidth, height: itemWidth)
        let scale = UIScreen.main.scale
        thumbnailSize = CGSize(width: itemWidth * scale, height: itemWidth * scale)
    }
    
    private var itemWidth: CGFloat  {
        let scale = UIScreen.main.scale
        let width = floor(scale * (view.bounds.width - padding * (column - 1)) / column) / scale
        return width
    }
    
    private func loadAllAsset() {
        PHPhotoLibrary.shared().register(self)
        if fetchResult == nil {
            let allPhotosOptions = PHFetchOptions()
            allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
            fetchResult = PHAsset.fetchAssets(with: allPhotosOptions)
        }
    }
    
    func scrollToPosition(to position: Int) {
        let indexPath = IndexPath(item: position, section: 0)
        if collectionView.indexPathsForVisibleItems.contains(indexPath) {
            return
        }
        collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
    }
}

// MARK: CachedAssets
extension AssetGridViewController {
    private func resetCachedAsset() {
        imageManager.stopCachingImagesForAllAssets()
        previousPreheatRect = .zero
    }
    
    private func updateCachedAssets() {
        guard isViewLoaded && view.window != nil else { return }
        
        let visiableRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
        let preheatRect = visiableRect.insetBy(dx: 0, dy: -0.5 * visiableRect.height)
        let delta = abs(preheatRect.midY - previousPreheatRect.midY)
        guard delta > view.bounds.height / 3.0 else { return }
        
        let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect)
        let addedAssets = addedRects
            .flatMap { rect in collectionView.indexPathsForElements(in: rect) }
            .map { indexPath in fetchResult.object(at: indexPath.item) }
        let removedAssets = removedRects
            .flatMap { rect in collectionView.indexPathsForElements(in: rect) }
            .map { indexPath in fetchResult.object(at: indexPath.item) }
        
        imageManager.startCachingImages(for: addedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
        imageManager.stopCachingImages(for: removedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
        previousPreheatRect = preheatRect
    }
    
    /// 计算两个rect的差异，添加一部分rect，然后删除一部分rect
    private func differencesBetweenRects(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) {
        if old.intersects(new) {
            var added = [CGRect]()
            if new.maxY > old.maxY {
                added += [CGRect(x: new.origin.x, y: old.maxY,
                                    width: new.width, height: new.maxY - old.maxY)]
            }
            if old.minY > new.minY {
                added += [CGRect(x: new.origin.x, y: new.minY,
                                    width: new.width, height: old.minY - new.minY)]
            }
            var removed = [CGRect]()
            if new.maxY < old.maxY {
                removed += [CGRect(x: new.origin.x, y: new.maxY,
                                      width: new.width, height: old.maxY - new.maxY)]
            }
            if old.minY < new.minY {
                removed += [CGRect(x: new.origin.x, y: old.minY,
                                      width: new.width, height: new.minY - old.minY)]
            }
            return (added, removed)
        } else {
            return ([new], [old])
        }
    }
    
}

// MARK: UIScrollViewDelegate
extension AssetGridViewController {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateCachedAssets()
    }
}

// MARK: UICollectionViewDelegate
extension AssetGridViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    func numberOfSections(in collectionView: UICollectionView) -> Int { 1 }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        fetchResult.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let asset = fetchResult.object(at: indexPath.item)
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: AssetGridCell.self), for: indexPath) as! AssetGridCell
        cell.representedAssetIdentifier = asset.localIdentifier
        imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) { (image, _) in
            if cell.representedAssetIdentifier == asset.localIdentifier {
                cell.thumbnailImage = image
            }
        }
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)
        let asset = AssetViewController()
        asset.fetchResult = fetchResult
        asset.currentIndex = indexPath.item
        asset.selectedIndexChanged = { selectedIndex in
            self.scrollToPosition(to: selectedIndex)
        }
        navigationController?.pushViewController(asset, animated: true)
    }
}

// MARK: PHPhotoLibraryChangeObserver
extension AssetGridViewController: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard let changes = changeInstance.changeDetails(for: fetchResult) else { return }
        DispatchQueue.main.sync {
            fetchResult = changes.fetchResultAfterChanges
            if changes.hasIncrementalChanges {
                collectionView.performBatchUpdates {
                    if let removed = changes.removedIndexes, removed.count > 0 {
                        collectionView.deleteItems(at: removed.map({ IndexPath(item: $0, section: 0) }))
                    }
                    if let inserted = changes.insertedIndexes, inserted.count > 0 {
                        collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: 0) }))
                    }
                    if let changed = changes.changedIndexes, changed.count > 0 {
                        collectionView.reloadItems(at: changed.map({ IndexPath(item: $0, section: 0) }))
                    }
                    changes.enumerateMoves { fromIndex, toIndex in
                        self.collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0), to: IndexPath(item: toIndex, section: 0))
                    }
                }
            } else {
                collectionView.reloadData()
            }
            resetCachedAsset()
        }
    }
}

private extension UICollectionView {
    func indexPathsForElements(in rect: CGRect) -> [IndexPath] {
        let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
        return allLayoutAttributes.map { $0.indexPath }
    }
}


